package com.viknix.threadpool.manager.server.dependence.util;

import com.viknix.threadpool.manager.server.dependence.constant.Constants;
import com.viknix.threadpool.manager.server.dependence.monitor.NotifyCenter;
import com.viknix.threadpool.manager.server.dependence.monitor.event.SlowEvent;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;

import java.io.IOException;
import java.net.*;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.Objects;

import static com.viknix.threadpool.manager.server.dependence.constant.Constants.*;

/**
 * @Author: Dongqi
 * @Date: 2021/11/11 10:16
 * @Version 1.0
 * @Description: 网络操作工具类
 */
@Slf4j
public class InetUtils {

    private static String selfIP;

    private static boolean useOnlySiteLocalInterface = false;

    private static boolean preferHostnameOverIP = false;

    private static final List<String> PREFERRED_NETWORKS = new ArrayList<String>();

    private static final List<String> IGNORED_INTERFACES = new ArrayList<String>();

    static {
        NotifyCenter.registerToSharePublisher(IPChangeEvent.class);

        useOnlySiteLocalInterface = Boolean.parseBoolean(EnvUtil.getProperty(USE_ONLY_SITE_INTERFACES));

        List<String> networks = EnvUtil.getPropertyList(Constants.PREFERRED_NETWORKS);
        PREFERRED_NETWORKS.addAll(networks);

        List<String> interfaces = EnvUtil.getPropertyList(Constants.IGNORED_INTERFACES);
        IGNORED_INTERFACES.addAll(interfaces);

        Runnable ipAutoRefresh = new Runnable() {
            @Override
            public void run() {
                String tpmIP = System.getProperty(TPM_SERVER_IP);
                if (StringUtils.isBlank(tpmIP)) {
                    tpmIP = EnvUtil.getProperty(IP_ADDRESS);
                }
                if (!StringUtils.isBlank(tpmIP)) {
                    if (!(IPUtil.isIP(tpmIP) || isDomain(tpmIP))) {
                        throw new RuntimeException("tpm address " + tpmIP + " is not ip");
                    }
                }
                String tmpSelfIP = tpmIP;
                if (StringUtils.isBlank(tmpSelfIP)) {
                    preferHostnameOverIP = Boolean.getBoolean(SYSTEM_PREFER_HOSTNAME_OVER_IP);

                    if (!preferHostnameOverIP) {
                        preferHostnameOverIP = Boolean
                                .parseBoolean(EnvUtil.getProperty(PREFER_HOSTNAME_OVER_IP));
                    }

                    if (preferHostnameOverIP) {
                        InetAddress inetAddress;
                        try {
                            inetAddress = InetAddress.getLocalHost();
                            if (inetAddress.getHostName().equals(inetAddress.getCanonicalHostName())) {
                                tmpSelfIP = inetAddress.getHostName();
                            } else {
                                tmpSelfIP = inetAddress.getCanonicalHostName();
                            }
                        } catch (UnknownHostException ignore) {
                            log.warn(">>> [😒 Unable to retrieve localhost] : {}", ignore.getMessage());
                        }
                    } else {
                        // 获取本地的 ip 地址
                        tmpSelfIP = Objects.requireNonNull(findFirstNonLoopbackAddress()).getHostAddress();
                    }
                }
                if (IPUtil.PREFER_IPV6_ADDRESSES && !tmpSelfIP.startsWith(IPUtil.IPV6_START_MARK) && !tmpSelfIP
                        .endsWith(IPUtil.IPV6_END_MARK)) {
                    tmpSelfIP = IPUtil.IPV6_START_MARK + tmpSelfIP + IPUtil.IPV6_END_MARK;
                    if (StringUtils.contains(tmpSelfIP, IPUtil.PERCENT_SIGN_IN_IPV6)) {
                        tmpSelfIP = tmpSelfIP.substring(0, tmpSelfIP.indexOf(IPUtil.PERCENT_SIGN_IN_IPV6))
                                + IPUtil.IPV6_END_MARK;
                    }
                }

                if (!Objects.equals(selfIP, tmpSelfIP) && Objects.nonNull(selfIP)) {
                    IPChangeEvent event = new IPChangeEvent();
                    event.setOldIP(selfIP);
                    event.setNewIP(tmpSelfIP);
                    NotifyCenter.publishEvent(event);
                }
                selfIP = tmpSelfIP;
            }
        };

        ipAutoRefresh.run();
    }

    /**
     * 获取本地 ip 地址
     *
     * @return 本地 ip 地址
     */
    public static String getSelfIP() {
        return selfIP;
    }

    /**
     * findFirstNonLoopbackAddress.
     *
     * @return {@link InetAddress}
     */
    public static InetAddress findFirstNonLoopbackAddress() {
        InetAddress result = null;

        try {
            int lowest = Integer.MAX_VALUE;
            for (Enumeration<NetworkInterface> nics = NetworkInterface.getNetworkInterfaces();
                 nics.hasMoreElements(); ) {
                NetworkInterface ifc = nics.nextElement();
                if (ifc.isUp()) {
                    log.debug(">>> [😐 Testing interface] : " + ifc.getDisplayName());
                    if (ifc.getIndex() < lowest || result == null) {
                        lowest = ifc.getIndex();
                    } else {
                        continue;
                    }

                    if (!ignoreInterface(ifc.getDisplayName())) {
                        for (Enumeration<InetAddress> addrs = ifc.getInetAddresses(); addrs.hasMoreElements(); ) {
                            InetAddress address = addrs.nextElement();
                            boolean isLegalIpVersion = IPUtil.PREFER_IPV6_ADDRESSES ? address instanceof Inet6Address
                                    : address instanceof Inet4Address;
                            if (isLegalIpVersion && !address.isLoopbackAddress() && isPreferredAddress(address)) {
                                log.debug(">>> [😐 Found non-loopback interface] : " + ifc.getDisplayName());
                                result = address;
                            }
                        }
                    }
                }
            }
        } catch (IOException ex) {
            log.error(">>> 😒 Cannot get first non-loopback address : {}", ex);
        }

        if (result != null) {
            return result;
        }

        try {
            return InetAddress.getLocalHost();
        } catch (UnknownHostException e) {
            log.warn(">>> 😒 Unable to retrieve localhost");
        }

        return null;
    }

    private static boolean isPreferredAddress(InetAddress address) {
        if (useOnlySiteLocalInterface) {
            final boolean siteLocalAddress = address.isSiteLocalAddress();
            if (!siteLocalAddress) {
                log.debug(">>> 😐 Ignoring address: " + address.getHostAddress());
            }
            return siteLocalAddress;
        }
        if (PREFERRED_NETWORKS.isEmpty()) {
            return true;
        }
        for (String regex : PREFERRED_NETWORKS) {
            final String hostAddress = address.getHostAddress();
            if (hostAddress.matches(regex) || hostAddress.startsWith(regex)) {
                return true;
            }
        }

        return false;
    }

    private static boolean ignoreInterface(String interfaceName) {
        for (String regex : IGNORED_INTERFACES) {
            if (interfaceName.matches(regex)) {
                log.debug(">>> 😐 Ignoring interface: " + interfaceName);
                return true;
            }
        }
        return false;
    }

    /**
     * juege str is right domain.
     *
     * @param str tpmIP
     * @return tpmIP is domain
     */
    public static boolean isDomain(String str) {
        InetSocketAddress address = new InetSocketAddress(str, 0);
        boolean unResolved = address.isUnresolved();
        if (unResolved) {
            log.warn(">>> 😒 the domain: '" + str + "' can not be resolved");
        }
        return !unResolved;
    }

    @SuppressWarnings({"PMD.ClassNamingShouldBeCamelRule", "checkstyle:AbbreviationAsWordInName"})
    public static class IPChangeEvent extends SlowEvent {

        private String oldIP;

        private String newIP;

        public String getOldIP() {
            return oldIP;
        }

        public void setOldIP(String oldIP) {
            this.oldIP = oldIP;
        }

        public String getNewIP() {
            return newIP;
        }

        public void setNewIP(String newIP) {
            this.newIP = newIP;
        }

        @Override
        public String toString() {
            return "IPChangeEvent{" + "oldIP='" + oldIP + '\'' + ", newIP='" + newIP + '\'' + '}';
        }
    }

}
